package cn.b.sky.schedule.job;

import cn.b.sky.common.utils.SpringBeanUtil;
import cn.b.sky.schedule.service.TimedJobService;
import org.quartz.*;
import org.quartz.impl.JobDetailImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.JobMethodInvocationFailedException;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.QuartzJobBean;
import org.springframework.util.ClassUtils;
import org.springframework.util.MethodInvoker;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 
 * <strong>Title : QuartzJobInvocationHandler.java<br></strong>
 * <strong>Package : cn.b.sky.schedule.job<br></strong>
 * <strong>Description : </strong>定时任务代理类<br> 
 * <strong>Create on : 2016-6-17 上午10:54:04<br></strong>
 * <p>
 * @author lidapeng<br>
 * @version <strong>v1.0.0</strong><br>
 * <br>
 * <strong>修改历史:</strong><br>
 * 修改人|修改日期|修改描述<br>
 * -------------------------------------------<br>
 * <br>
 * <br>
 */
public class QuartzJobInvocationHandler extends MethodInvoker {
  private static final Logger LOGGER = LoggerFactory.getLogger(QuartzJobInvocationHandler.class);
  
  private static Method setResultMethod;

  private String group = Scheduler.DEFAULT_GROUP;

  private String name;

  private JobDetail jobDetail;

  private boolean concurrent = true;
  
  protected final TimedJobService timedJobService = (TimedJobService) SpringBeanUtil.getBean("timedJobService");

  static {
    try {
      Class<?> jobExecutionContextClass = ClassUtils.forName("org.quartz.JobExecutionContext",
          MethodInvokingJobDetailFactoryBean.class.getClassLoader());
      setResultMethod = jobExecutionContextClass.getMethod("setResult", Object.class);
    } catch (Exception ex) {
      throw new IllegalStateException("Incompatible Quartz API: " + ex);
    }
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getGroup() {
    return group;
  }

  public void setGroup(String group) {
    this.group = group;
  }

  /**
   * 获取代理的job类
   */
  public JobDetail getJobDetail() {
    Class<?> jobClass = (this.concurrent ? MethodInvokingJob.class : StatefulMethodInvokingJob.class);
    this.jobDetail = JobBuilder.newJob().storeDurably().build();
    this.jobDetail.getJobDataMap().put("methodInvoker", this);
    this.jobDetail.getJobDataMap().put("timedJobService", timedJobService);
    return jobDetail;
  }

  public void setJobDetail(JobDetail jobDetail) {
    this.jobDetail = jobDetail;
  }

  public boolean isConcurrent() {
    return concurrent;
  }

  public void setConcurrent(boolean concurrent) {
    this.concurrent = concurrent;
  }

  /**
   * Quartz Job implementation that invokes a specified method.
   * Automatically applied by MethodInvokingJobDetailFactoryBean.
   */
  public static class MethodInvokingJob extends QuartzJobBean {
    
    private MethodInvoker methodInvoker;
    
    private TimedJobService timedJobService;

    public MethodInvoker getMethodInvoker() {
      return methodInvoker;
    }

    public void setMethodInvoker(MethodInvoker methodInvoker) {
      this.methodInvoker = methodInvoker;
    }

    public TimedJobService getTimedJobService() {
      return timedJobService;
    }

    public void setTimedJobService(TimedJobService timedJobService) {
      this.timedJobService = timedJobService;
    }

    /**
     * Invoke the method via the MethodInvoker.
     */
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
      try {
        ReflectionUtils.invokeMethod(setResultMethod, context, this.methodInvoker.invoke());
      }
      catch (InvocationTargetException ex) {
        LOGGER.error("{}",ex);
        if (ex.getTargetException() instanceof JobExecutionException) {
          // -> JobExecutionException, to be logged at info level by Quartz
          throw (JobExecutionException) ex.getTargetException();
        } else {
          // -> "unhandled exception", to be logged at error level by Quartz
          throw new JobMethodInvocationFailedException(this.methodInvoker, ex.getTargetException());
        }
      } catch (Exception ex) {
        // -> "unhandled exception", to be logged at error level by Quartz
        throw new JobMethodInvocationFailedException(this.methodInvoker, ex);
      }finally{
        //调整最后执行时间更新规则，防止作业抛异常后未更新执行时间
        //更新执行时间
        timedJobService.updateLastExecTime(context.getJobDetail().getKey().getName());
      }
    }
  }

  /**
   * Extension of the MethodInvokingJob, implementing the StatefulJob interface.
   * Quartz checks whether or not jobs are stateful and if so,
   * won't let jobs interfere with each other.
   */
  public static class StatefulMethodInvokingJob extends MethodInvokingJob implements StatefulJob {

    // No implementation, just an addition of the tag interface StatefulJob
    // in order to allow stateful method invoking jobs.
  }

}
